
package ija.model;

import ija.model.basis.Desk;
import ija.model.basis.Figure;
import ija.model.basis.Position;
import ija.model.figures.CheckersFigure;
import ija.model.figures.Piece;
import ija.model.figures.Queen;
import ija.model.players.Player;
import ija.model.types.Color;
import ija.model.types.Direction;
import ija.xls.notation.GameMove;
import ija.xls.notation.types.GameOperation;

/**
 * Hlavní model pro hru dáma.
 * Reprezentuje datový model MVC architektury využívaný pro běh hry.
 * Implementuje návrhový vzor Observer, pomocí kterého informuje View či Net o svých změnách
 * 
 * @author Radek Necas, xnecas18
 */
public class CheckersModel extends java.util.Observable implements GameModel {
    static final int PLAYERS_COUNT = 2;
    
    protected Desk desk;
    Player[] players;
    int activePlayer;
   
    /**
     * Konstruktor vytváří model.
     */
    public CheckersModel() {
        desk = new Desk();
        activePlayer = 0;
        players = new Player[PLAYERS_COUNT];
    }
    
    /**
     * Inicializování hrací desky.
     * Metoda naplní šachovnici figurkami do základního postavení.
     * @param topPlayerColor udává jakou barvu budou mít figurky v horní části šachovnice (řádky: 8, 7, 6).
     */
    @Override
    public void initGame(Color topPlayerColor) {
        int dim = desk.getDimension();
        int halfDim = dim/2;
        
        Color topColor = topPlayerColor;
        Color downColor = (topPlayerColor == Color.BLACK ? Color.WHITE : Color.BLACK);
        
        Position p;
        
        for(int row = 1; row <= dim; row++) {
            // Vyplnuji se pouze tri radky na zacatku a na konci pole
            if(row > 3 && row <= dim - 3)
                continue;
                
            for(int col = 0; col < dim; col++) {
                // Na sudych radcich vyplnime liche sloupce
                if(row % 2 == 0) {
                    if(col % 2 != 0) {
                        p = desk.getPositionAt((char)(col+'a'), row);
                        // Vyber adekvatni figurky (nahore cerna, dole bila)
                        if(row > halfDim)                    
                            //p.putFigure(new PieceGoBack(p, topColor));
                            p.putFigure(new Piece(p, topColor, Direction.BACK));
                        else
                            //p.putFigure(new PieceGoForward(p, downColor));                 
                            p.putFigure(new Piece(p, downColor, Direction.FORWARD));                 
                    }
                }
                
                // Na lichych radcich vyplnuji sude sloupce
                else {
                    if(col % 2 == 0) {
                        p = desk.getPositionAt((char)(col+'a'), row);
                        // Vyber adekvatni figurky (nahore cerna, dole bila)
                        if(row > halfDim)                    
                            //p.putFigure(new PieceGoBack(p, topColor));
                            p.putFigure(new Piece(p, topColor, Direction.BACK));
                        else
                            //p.putFigure(new PieceGoForward(p, downColor));
                            p.putFigure(new Piece(p, downColor, Direction.FORWARD));
                    }
                }
            }   // end for(ALL_ROWS)
        }   //end for(ALL_COLUMNS)
    }   // end initGame()
    
    
    /**
     * Metoda pro nastavení hráče
     * @param id identifikator hráče, jenž se bude nastavovat (povolené 0 a 1).
     * @param p hráč který se nastaví (zkopíruje se odkaz)
     * @return true pokud je hráč nastaven, false při chybě (špatné id)
     */
    @Override
    public boolean setPlayer(int id, Player p) {
        if(id >= 0 && id < PLAYERS_COUNT) {
            players[id] = p;
            return true;
        }
        
        return false;
    }
    
    /**
     * Metoda pro získání hráče.
     * @param id identifikátor hráče (povolené hodnoty 0 a 1).
     * @return hráč nebo null při chybě (špatné id)
     */
    @Override
    public Player getPlayer(int id) {
        if(id >= 0 && id < 2)
            return players[id];
        
        return null;
    }
    
    /**
     * Vrátí aktivního hráče
     * @return aktivní hráč
     */
    @Override
    public Player getActivePlayer() {
        return getPlayer(activePlayer);
    }
    
    /**
     * Aktivuje určeného hráče.
     * Pokud není metoda volána, je automaticky aktivní hráč 0.
     * @param id identifikátor hráče, jenž má být aktivován
     * @return true ok, jinak false (špatné id)
     */
    @Override
    public boolean activatePlayer(int id) {
        if(id >= 0 && id < PLAYERS_COUNT) {
            if(players[id] != null) {
                activePlayer = id;
                return true;
            }
        }
        return false;
    }
    
    /**
     * Aktivuje dalšího hráče (toho, který je na řadě).
     */
    @Override
    public void nextPlayer() {
        if(activePlayer == 0)
            activePlayer++;
        else if(activePlayer == 1)
            activePlayer--;
    }
    
    /**
     * Metoda pro získání šachovnice.
     * Skrze šachovnici se poté získávají další informace (pozice, figurky).
     * Získané objekty slouží jen pro čtení. Neměli by se přímo měnit.
     * 
     * @return šachovnice, v níž jsou umístěny figurky 
     */
    @Override
    public Desk getDesk() {
        return desk;
    }
    
    /**
     * Provádí pohyb figurky.
     * Žádaná operace (skok, pohyb) se zjisti z parametru moveDescription
     * Aktualizuje všechny související objekty (figurky, pozice v dané šachovnici).
     * Pozice musí být na téže šachovnici (ale není testováno).
     * O změně informuje své posluchače (Observer).
     * 
     * @param moveDescription popisuje pohyb (počáteční, koncová pozice, operace)
     * @return true pokud dojde ke změně modelu, jinak false
     */
    @Override
    public boolean move(GameMove moveDescription) {
        if(moveDescription != null) {
            Position startPos = desk.getPositionAt(moveDescription.getFirstPosition().getColumn(), moveDescription.getFirstPosition().getRow());
            Position endPos = desk.getPositionAt(moveDescription.getLastPosition().getColumn(), moveDescription.getLastPosition().getRow());
            GameOperation op = moveDescription.getOperation();
            
            if(op == GameOperation.MOVE)
                return move(startPos, endPos);
            else if(op == GameOperation.JUMP)
                return jump(startPos, endPos);
        }
        
        return false;
    }
    
    /**
     * Provádí pohyb figurky.
     * Aktualizuje všechny související objekty (figurky, pozice v dané šachovnici).
     * Pozice musí být na téže šachovnici (ale není testováno).
     * 
     * @param startPosition počáteční pozice. Zde se nachází pohybovaná figurka.
     * @param endPosition cílová pozice pohybu.
     * @return pokud je pohyb proveden true, jinak false.
     */
    public boolean move(Position startPosition, Position endPosition) {
        if(!desk.isInside(startPosition) || !desk.isInside(endPosition) || startPosition.getFigure() == null) {
            return false;
        }
        
        // Bezny pohyb figurkou
        boolean moveOk = startPosition.getFigure().move(endPosition);
        if(moveOk) {
            setQueen(endPosition);          // Test a provedeni nasazeni figurky
            setChanged();
            notifyObservers();
        }
        
        return moveOk;
    }
    
    
    /**
     * Provádí skok figurky.
     * Mezi počáteční a cílovou pozicí musí být figurka protihráče.
     * Provádí pouze jednoduchý skok. Víceskoky se reprezentují vícenásobným voláním metody.
     * Aktualizuje všechny související objekty (počáteční pozici a figurku na ní, přeskakovanou figurku a její pohyb, cílovou pozici).
     * 
     * @param startPosition počáteční pozice skoku. Zde se nachází figurka s níž chceme skok provést.
     * @param endPosition cílová pozice skoku.
     * @return pokud je skok proveden true, jinak false.
     */
    public boolean jump(Position startPosition, Position endPosition) {
        if(!desk.isInside(startPosition) || !desk.isInside(endPosition) || startPosition.getFigure() == null) {
            return false;
        }
        
        CheckersFigure f =(CheckersFigure) startPosition.getFigure();
        boolean jumpOk = f.jump(endPosition);
        if(jumpOk) {
            setChanged();
            notifyObservers();
        }
        
        return jumpOk;
    }
    
    /**
     * Test zda může být na dané pozici nasazena královna.
     * 
     * @param p testovaná pozice
     * @return pokud test projde true, jinak false.
     */
    public boolean canSetQueen(Position p) {
        if(!desk.isInside(p) || p.getFigure() == null) {
            return false;
        }
        
        Figure f = p.getFigure();
        if(f instanceof Piece){
            Piece piece = (Piece)f;
            if(piece.getDirection() == Direction.FORWARD && p.getRowNumber() == desk.getDimension()) {
                return true;
            }
            else if(piece.getDirection() == Direction.BACK && p.getRowNumber() == 1) {
                return true;
            }
        }
        
        /*
        if(f instanceof PieceGoForward) {
            if(p.getRowNumber() == desk.getDimension())
                return true;
        }
        else if(f instanceof PieceGoBack) {
            if(p.getRowNumber() == 1)
                return true;
        }
        */
        
        return false;
    }
    
    
    /**
     * Metoda pro nasazení královny.
     * Pouze provádí záměnu figurek (pěšce za královnu).
     * Neprovádí posuv na nasazovací pozici (už očekává, že tam figurka je).
     * 
     * @param p pozice, kde se má nasadit dáma.
     * @return při nasazení true, jinak false.
     */
    public boolean setQueen(Position p) {
        if(canSetQueen(p)) {
            Figure f = p.removeFigure();
            p.putFigure(new Queen(p, f.getColor()));
            
            return true;
        }
        
        return false;
    }

}
